#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/proc_fs.h>

#define GPIOSTB_CNT 1 /* 设备号个数 */
#define GPIOSTB_NAME "gpiostb" /* 名字 */
#define STBLOW 0 /* set STB LOW */
#define STBHIGH 1 /* set STB HIGH */

/* gpiostb 设备结构体 */
struct gpiostb_dev{
    dev_t devid; /* 设备号 */
    struct cdev cdev; /* cdev */
    struct class *class; /* 类 */
    struct device *device; /* 设备 */
    int major; /* 主设备号 */
    int minor; /* 次设备号 */
    struct device_node *nd; /* 设备节点 */
    int stb_gpio; /* led 所使用的 GPIO 编号 */
};

struct gpiostb_dev gpiostb; /* led 设备 */

/*
* @description : 打开设备
* @param - inode : 传递给驱动的 inode
* @param - filp : 设备文件，file 结构体有个叫做 private_data 的成员变量
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int stb_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &gpiostb; /* 设置私有数据 */
    return 0;
}

/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数，如果为负值，表示读取失败
*/
static ssize_t stb_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

/*
* @description : 向设备写数据
* @param - filp : 设备文件，表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数，如果为负值，表示写入失败
*/
static ssize_t stb_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    char *str, *cmd, *value;
    unsigned char tmpbuf[64];
    uint32_t stbstat;
    struct gpiostb_dev *dev = filp->private_data;

    if (cnt >= sizeof(tmpbuf))
        goto error;

    if (!buf || copy_from_user(tmpbuf, buf, cnt) != 0)
        return 0;

    if (cnt > 0)
    {
        str = tmpbuf;
        value = strsep(&str, "\t \n");
        if (!value)
        {
            goto error;
        }
        stbstat = simple_strtoul(value, &value, 16);
        printk("\r\n write stbstat = 0x%d stbstat = 0x%x\r\n", stbstat, stbstat);
        if(stbstat == STBHIGH) { 
            gpio_set_value(dev->stb_gpio, 1); /* set STB HIGH */
        } else if(stbstat == STBLOW) {
            gpio_set_value(dev->stb_gpio, 0); /* set STB LOW */
        }
    }
    return cnt;
error:
    printk("usage: \n");
    printk(" set stb HIGH: for example; echo 1 > /dev/gpiostb\n");
    printk(" set stb LOW: for example; echo 0 > /dev/gpiostb\n");
    return -EFAULT;

    // int retvalue;
    // unsigned char databuf[0];
    // unsigned char stbstat;
    // struct gpiostb_dev *dev = filp->private_data;

    // retvalue = copy_from_user(databuf, buf, cnt); /* 接收 APP 发送过来的数据 */
    // if(retvalue < 0) {
    //     printk("kernel write failed!\r\n");
    //     return -EFAULT;
    // }

    // stbstat = databuf[0]; /* 获取状态值 */

    // if(stbstat == STBHIGH) { 
    //     gpio_set_value(dev->stb_gpio, 1); /* set STB HIGH */
    // } else if(stbstat == STBLOW) {
    //     gpio_set_value(dev->stb_gpio, 0); /* set STB LOW */
    // }
    // return 0;
}

static ssize_t stb_write_proc(struct file *filp, const char __user *buffer, size_t count, loff_t *offt)
{
    char *str, *cmd, *value;
    unsigned char tmpbuf[64];
    uint32_t stbstat;
    struct gpiostb_dev *dev = filp->private_data;

    if (count >= sizeof(tmpbuf))
        goto error;

    if (!buffer || copy_from_user(tmpbuf, buffer, count) != 0)
        return 0;

    if (count > 0)
    {
        str = tmpbuf;
        cmd = strsep(&str, "\t \n");
        /*char *strsep(char **stringP,const char *delim)； 
        参数说明 
            stringP： 要被分割的字符串地址，函数执行后该元素被更改，总是只想要被分割的字符串； 
            delim： 分割符； 
            返回值： 函数返回分割后的第一个字符串。函数执行的过程，是在 *stringp 中查找分割符，并将其替换为“\0”，
        返回分割出的第一个字符串指针 （NULL 表示到达字符串尾），并更新 *stringp 指向下一个字符串。 
            头文件： #include<string.h> */
        if (!cmd)
        {
            goto error;
        }
        if (strcmp(cmd, "write") == 0)
        {
            value = strsep(&str, "\t \n");
            if (!value)
            {
                goto error;
            }
            stbstat = simple_strtoul(value, &value, 16);
            /*simple_strtoul 是Linux内核中提供的一个字符串转换函数，
            它的功能是将一个字符串转换成无符号长整型（unsigned long）数据。这个函数在内核开发中经常被用来从字符串中解析出数字信息。
            函数的原型定义在 lib/vsprintf.c 文件中，其基本形式如下：
            unsigned long simple_strtoul(const char *cp, char **endp, unsigned int base);
                cp: 参数指向要转换的字符串的开始。
                endp: 参数是一个二级指针，用于存储字符串中不再是数字部分的首个字符的地址。
                base: 参数用于指定转换时使用的基数（进制数）。当 base 为0时，函数会自动判断字符串的基数。
            例如，如果字符串以 "0x" 开头，则被认为是16进制；如果以 "0" 开头，则被认为是8进制；其他情况下默认为10进制。*/
            printk("\r\n write stbstat = 0x%d stbstat = 0x%x\r\n", stbstat, stbstat);
            if(stbstat == STBHIGH) { 
                gpio_set_value(dev->stb_gpio, 1); /* set STB HIGH */
            } else if(stbstat == STBLOW) {
                gpio_set_value(dev->stb_gpio, 0); /* set STB LOW */
            }
        }
        else
        {
            goto error;
        }
    }
    return count;
error:
    printk("usage: \n");
    printk(" set stb HIGH: for example; echo write 1 > /dev/gpiostb\n");
    printk(" set stb LOW: for example; echo write 0 > /dev/gpiostb\n");
    return -EFAULT;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int stb_release(struct inode *inode, struct file *filp)
{
    return 0;
}
/* 设备操作函数 */
static struct file_operations gpiostb_fops = {
    .owner = THIS_MODULE,
    .open = stb_open,
    .read = stb_read,
    .write = stb_write,
    .release = stb_release,
};

static struct proc_dir_entry *stb_proc;

/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static int __init stb_init(void)
{
    int ret = 0;
    const char *str;
    /* 设置 LED 所使用的 GPIO */
    /* 1、获取设备节点：stb-set */
    gpiostb.nd = of_find_node_by_name(NULL,"stb-set");
    if(gpiostb.nd == NULL) {
        printk("gpiostb node not find!\r\n");
        return -EINVAL;
    }

    /* 2.读取 status 属性 */
    ret = of_property_read_string(gpiostb.nd, "status", &str);
    if(ret < 0)
        return -EINVAL;

    if (strcmp(str, "okay"))
        return -EINVAL;

    /* 3、 获取设备树中的 gpio 属性，得到 STB 所使用的 gpio 编号 */
    gpiostb.stb_gpio = of_get_named_gpio(gpiostb.nd, "gpios", 0);
    if(gpiostb.stb_gpio < 0) {
        printk("can't get gpios");
        return -EINVAL;
    }
    printk("stb-gpio num = %d\r\n", gpiostb.stb_gpio);

    /* 4.向 gpio 子系统申请使用 GPIO */
    ret = gpio_request(gpiostb.stb_gpio, "STB-GPIO");
    if (ret) {
        printk(KERN_ERR "gpiostb: Failed to request stb-gpio\n");
        return ret;
    }

    /* 5、设置 Pe18 为输出，并且输出低电平，默认启用CAN收发器 */
    ret = gpio_direction_output(gpiostb.stb_gpio, 0);
    if(ret < 0) {
        printk("can't set gpio!\r\n");
    }

    /* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (gpiostb.major) { /* 定义了设备号 */
        gpiostb.devid = MKDEV(gpiostb.major, 0);
        ret = register_chrdev_region(gpiostb.devid, GPIOSTB_CNT,
        GPIOSTB_NAME);
    if(ret < 0) {
        pr_err("cannot register %s char driver [ret=%d]\n",
        GPIOSTB_NAME, GPIOSTB_CNT);
        goto free_gpio;
    }

    } else { /* 没有定义设备号 */
        ret = alloc_chrdev_region(&gpiostb.devid, 0, GPIOSTB_CNT, GPIOSTB_NAME); /* 申请设备号 */
        if(ret < 0) {
            pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n",
            GPIOSTB_NAME, ret);
            goto free_gpio;
        }
        gpiostb.major = MAJOR(gpiostb.devid); /* 获取分配号的主设备号*/
        gpiostb.minor = MINOR(gpiostb.devid); /* 获取分配号的次设备号*/
    }
    printk("gpiostb major=%d,minor=%d\r\n",gpiostb.major,gpiostb.minor); 

    /* 2、初始化 cdev */
    gpiostb.cdev.owner = THIS_MODULE;
    cdev_init(&gpiostb.cdev, &gpiostb_fops);
    /* 3、添加一个 cdev */
    cdev_add(&gpiostb.cdev, gpiostb.devid, GPIOSTB_CNT);
    if(ret < 0)
        goto del_unregister;

    /* 4、创建类 */
    gpiostb.class = class_create(THIS_MODULE, GPIOSTB_NAME);
    if (IS_ERR(gpiostb.class)) {
        goto del_cdev;
    }

    /* 5、创建设备 */
    gpiostb.device = device_create(gpiostb.class, NULL, gpiostb.devid, NULL, GPIOSTB_NAME);
    if (IS_ERR(gpiostb.device)) {
        goto destroy_class;
    }

    return 0;

    destroy_class:
    class_destroy(gpiostb.class);
    del_cdev:
    cdev_del(&gpiostb.cdev);
    del_unregister:
    unregister_chrdev_region(gpiostb.devid, GPIOSTB_CNT);
    free_gpio:
    gpio_free(gpiostb.stb_gpio);
    return -EIO;
}

/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit stb_exit(void)
{
    /* 注销字符设备驱动 */
    cdev_del(&gpiostb.cdev);/* 删除 cdev */
    unregister_chrdev_region(gpiostb.devid, GPIOSTB_CNT); /* 注销设备号 */
    device_destroy(gpiostb.class, gpiostb.devid);/* 注销设备 */
    class_destroy(gpiostb.class);/* 注销类 */
    gpio_free(gpiostb.stb_gpio); /* 释放 GPIO */
}

module_init(stb_init);
module_exit(stb_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");